package com.lyy.util;


import java.io.*;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 文件操作工具类
 */
public class FileUtils{

    public final static int THREAD_NUMBER=10;

    /**
     * 写入文件
     * @param target
     * @param src
     * @throws IOException
     */
    public static void write(String target, InputStream src) throws IOException {
        OutputStream os = new FileOutputStream(target);
        byte[] buf = new byte[1024];
        int len;
        while (-1 != (len = src.read(buf))) {
            os.write(buf,0,len);
        }
        os.flush();
        os.close();
    }

    /**
     * 分块写入文件
     * @param target
     * @param targetSize
     * @param src
     * @param srcSize
     * @param chunks
     * @param chunk
     * @throws IOException
     */
    public static void writeWithBlock(String target, Long targetSize, InputStream src, Long srcSize, Integer chunks, Integer chunk) throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile(target,"rw");
        randomAccessFile.setLength(targetSize);
        if (chunk == chunks - 1) {
            randomAccessFile.seek(targetSize - srcSize);
        } else {
            randomAccessFile.seek(chunk * srcSize);
        }
        byte[] buf = new byte[1024];
        int len;
        while (-1 != (len = src.read(buf))) {
            randomAccessFile.write(buf,0,len);
        }
        randomAccessFile.close();
    }

    /**
     * 多线程分片上传文件
     * @param target 目标文件
     * @param source 源文件
     * @param chunkSize 分段上传文件的大小
     * @param fileSizeUnit 文件大小的单位：比如K、M、G
     * @throws Exception
     */
    public static void blockWriteByMultiThread(File target, File source, long chunkSize,String fileSizeUnit) throws Exception {
        long sourceLen = source.length();
        if(sourceLen>20*1024*1024*1024){
            throw new Exception("上传的大小不应该超过20G");
        }
        RandomAccessFile rafW=new RandomAccessFile(target,"rw");
        rafW.setLength(sourceLen);
        long serveSize=0l;
        //当文件大小小于100M时，采用单线程
        if(sourceLen<=100*1024*1024){
            serveSize=sourceLen;
        }else {
            if ("k".equalsIgnoreCase(fileSizeUnit)) {
                serveSize = chunkSize * 1024;
            } else if ("m".equalsIgnoreCase(fileSizeUnit)) {
                serveSize = chunkSize * 1024 * 1024;
            } else if ("g".equalsIgnoreCase(fileSizeUnit)) {
                serveSize = chunkSize * 1024 * 1024 * 1024;
            } else {
                throw new Exception("无法识别的文件大小单位");
            }
        }
        int chunks= (int) (sourceLen%serveSize==0?sourceLen/serveSize:sourceLen/serveSize+1);
        final CountDownLatch countDownLatchTotal=new CountDownLatch(chunks);
        ExecutorService executorService= Executors.newFixedThreadPool(THREAD_NUMBER);
        for(int i=0;i<chunks;i++){
            executorService.submit(new MyThread(countDownLatchTotal,serveSize,source,target,i));
        }
        countDownLatchTotal.await();
        rafW.close();
        executorService.shutdown();
    }

    /**
     * 以字节流缓冲的方式压缩文件
     * @param targetPath 压缩文件的目标路径
     * @param picPaths 要压缩的源文件路径
     * @throws Exception
     */
    public static void zipFileByteBuffer(String targetPath, List<String> picPaths) throws Exception {
        File targetFile=new File(targetPath);
        if(targetFile.exists()){
            targetFile.delete();
        }
        ZipOutputStream zipOut=new ZipOutputStream(new FileOutputStream(targetFile));
        for(String picPath:picPaths){
            File file=new File(picPath);
            FileInputStream fileInputStream = new FileInputStream(file);
            zipOut.putNextEntry(new ZipEntry(file.getName()));
            int len=0;
            byte[] bytes=new byte[1024];
            while((len=fileInputStream.read(bytes))!=-1){
                zipOut.write(bytes,0,len);
            }
            fileInputStream.close();
        }
        zipOut.close();
    }

    /**
     * 以channel的方式压缩文件，效率更高，省了内存中内核态与用户态之间的拷贝过程
     * @param targetPath 压缩文件的目标路径
     * @param picPaths 要压缩的源文件路径
     * @throws Exception
     */
    public static void zipFileChannel(String targetPath, List<String> picPaths) throws Exception {
        File targetFile=new File(targetPath);
        if(targetFile.exists()){
            targetFile.delete();
        }
        ZipOutputStream zipOut=new ZipOutputStream(new FileOutputStream(targetPath));
        WritableByteChannel writableByteChannel = Channels.newChannel(zipOut);
        FileChannel channel=null;
        for(String picPath:picPaths){
            File file=new File(picPath);
            channel = new FileInputStream(file).getChannel();
            zipOut.putNextEntry(new ZipEntry(file.getName()));
            channel.transferTo(0,file.length(),writableByteChannel);
            channel.close();
        }
        writableByteChannel.close();
        zipOut.close();
    }

    public static void unZipFile(String sourcePath,String targetPath) throws Exception {
        File sourceFile=new File(sourcePath);
        File targetFile=new File(targetPath);
        BufferedInputStream bufferedInputStream=null;
        BufferedOutputStream bufferedOutputStream=null;
        InputStream inputStream =null;
        OutputStream outputStream=null;
        ZipEntry entry =null;
        if(!sourceFile.exists()){
            throw new Exception("不存在源文件");
        }
        ZipFile zipFile=new ZipFile(sourceFile);
        ZipInputStream zipInputStream=new ZipInputStream(new FileInputStream(sourceFile));
        while((entry =zipInputStream.getNextEntry())!=null){
            if(!targetFile.exists()){
                targetFile.mkdirs();
            }
            inputStream = zipFile.getInputStream(entry);
            bufferedInputStream=new BufferedInputStream(inputStream);
            outputStream =new FileOutputStream(targetPath+File.separator+entry.getName());
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            int len=0;
            while((len=bufferedInputStream.read())!=-1){
                bufferedOutputStream.write(len);
            }
            bufferedOutputStream.close();
            bufferedInputStream.close();
            outputStream.close();
            inputStream.close();
        }
        zipInputStream.close();
    }
    /**
     * 生成随机文件名
     * @return
     */
    public static String generateFileName() {
        return UUID.randomUUID().toString();
    }

    static class MyThread implements Runnable{
        private CountDownLatch countDownLatchTotal;
        private Long serveSize;
        private File sourceFile;
        private File targetFile;
        private Integer threadNum;

        public MyThread(CountDownLatch countDownLatchTotal,Long serveSize,File sourceFile,
                        File targetFile,Integer threadNum){
            this.countDownLatchTotal=countDownLatchTotal;
            this.serveSize=serveSize;
            this.sourceFile=sourceFile;
            this.threadNum=threadNum;
            this.targetFile=targetFile;
        }

        @Override
        public void run() {
            try(RandomAccessFile rafR=new RandomAccessFile(sourceFile,"r");
                RandomAccessFile rafW=new RandomAccessFile(targetFile,"rw")) {
                rafR.seek(threadNum *serveSize);
                rafW.seek(threadNum *serveSize);
                byte[] bytes=new byte[1024];
                int len=0;
                Long maxSize=0l;
                while ((len=rafR.read(bytes))!=-1&&maxSize<serveSize/1024){
                    rafW.write(bytes,0,len);
                    maxSize++;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                countDownLatchTotal.countDown();
            }
        }
    }

}
